package com.wits100.av;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by chuanjiang.zh on 2016-08-15.
 */
public class SimpleVideoRender implements VideoRender, Runnable {

    public static final String TAG = "player";

    public SimpleVideoRender() {

    }

    @Override
    public boolean open(MFormat fmt, Surface surface) {
        synchronized (this) {
            mFormat = fmt;
            mSurface = surface;

            openCodec();
        }

        startThread();
        return true;
    }

    @Override
    public void close() {
        stopThread();
    }

    @Override
    public boolean isOpen() {
        return isRunning();
    }

    @Override
    public void setDisplay(Surface surface) {
        synchronized (this) {
            if (mSurface == surface) {
                //
            } else {
                mSurface = surface;

                openCodec();
            }
        }
    }

    @Override
    public void input(MPacket pkt) {
        packets.add(pkt);

        notifyPacket();
    }

    private void doInput(MPacket pkt) {
        synchronized (this) {
            if (mSurface == null) {
                return;
            }

            if (!isCodecOpen()) {
                return;
            }

            decode(pkt);

            doRender();
        }
    }

    private void decode(MPacket pkt) {
        final int TIMEOUT_USEC = 10 * 1000;
        ByteBuffer[] decoderInputBuffers = mCodec.getInputBuffers();
        int inputBufIndex = mCodec.dequeueInputBuffer(TIMEOUT_USEC);
        if (inputBufIndex >= 0) {
            ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
            inputBuf.put(pkt.data);
            mCodec.queueInputBuffer(inputBufIndex, 0, pkt.length(),
                    pkt.ts, pkt.flags);

        }
    }

    private void doRender() {
        int ret = mCodec.dequeueOutputBuffer(mBufferInfo, 0);
        if (ret == MediaCodec.INFO_TRY_AGAIN_LATER) {

        } else if (ret == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {

        } else if (ret == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

        } else if (ret >= 0) {
            mCodec.releaseOutputBuffer(ret, true);
        }

    }


    private boolean openCodec() {
        closeCodec();

        if (mSurface == null) {
            return false;
        }

        try {
            mediaFormat = mFormat.toVideoFormat();
            String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
            mCodec = MediaCodec.createDecoderByType(mime);
            mCodec.configure(mediaFormat, mSurface, null, 0);
            mCodec.start();
            return true;
        } catch (Throwable e) {
            if (mCodec != null) {
                mCodec.release();
                mCodec = null;
            }

            e.printStackTrace();
        }
        return false;
    }

    private void closeCodec() {
        if (mCodec != null) {
            try {
                mCodec.stop();
                mCodec.release();
            } finally {
                mCodec = null;
            }
        }
    }

    private boolean isCodecOpen() {
        return mCodec != null;
    }


    MFormat mFormat;
    MediaCodec mCodec;
    Surface mSurface;
    MediaFormat mediaFormat;

    Thread  mThread;
    boolean mCanExit = false;
    Queue<MPacket> packets = new ConcurrentLinkedDeque<>();
    final Lock lock = new ReentrantLock();//锁对象
    final Condition notEmptyCondition  = lock.newCondition();
    TimestampCounter timestampCounter = new TimestampCounter();

    private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();


    @Override
    public void run() {

        while (!mCanExit) {
            MPacket packet = getPacket(500);
            if (packet == null) {
                continue;
            }

            long delay = computeDelay(packet);
            if (delay > 0) {
                delay(delay);
            }

            doInput(packet);
        }

        closeCodec();
    }

    private void startThread() {
        if (isRunning()) {
            return;
        }

        synchronized (this) {
            this.mCanExit = false;
        }

        mThread = new Thread(this);
        mThread.start();
    }

    private void stopThread() {
        synchronized (this) {
            mCanExit = true;

            if (mThread != null) {
                mThread.interrupt();
            }

            mThread = null;
        }
    }

    private boolean isRunning() {
        return (mThread != null) && mThread.isAlive();
    }

    private MPacket getPacket(long ms) {
        if (packets.size() > 0) {
            return packets.remove();
        }

        waitPacket(ms);

        if (packets.size() > 0) {
            return packets.remove();
        }

        return null;
    }

    private  long computeDelay(MPacket packet) {
        long delay = timestampCounter.getDelta(packet.ts / 1000);
        //Log.i(TAG, "queue: " + packets.size() + " delay: " + delay);
        return delay;
    }

    private void delay(long ms) {
        if (ms < 5) {
            return;
        }

        try {
            //Thread.sleep(ms, 0);
            synchronized (mThread) {
                mThread.wait(ms);
            }
        } catch (InterruptedException ex) {
            //
        }
    }

    private void notifyPacket() {
        lock.lock();
        try {
            notEmptyCondition.signal();
        } finally {
            lock.unlock();
        }
    }

    private void waitPacket(long ms) {
        lock.lock();
        try {
            notEmptyCondition.await(ms, TimeUnit.MILLISECONDS);
        } catch (InterruptedException ex) {
            //
        } finally {
            lock.unlock();
        }
    }


}
